
#include "dotxsi.h"
#include "graphics_functions.h"

using namespace WtfGraphics;
using namespace WtfEngine;


#define XSI_VEC_TO_VEC(v) tVector(v.GetX(), v.GetY(), v.GetZ())
#define XSI_VEC_TO_VEC2(v) fVector2D(v.GetX(), v.GetY())

#define GET_XSIPARAM_FLOATVAL(pParamOwner,ParamName) pParamOwner->ParameterFromName(ParamName)->GetFloatValue()
#define GET_XSIPARAM_BOOLVAL(pParamOwner,ParamName) ((CSLVariantParameter*)pParamOwner->ParameterFromName(ParamName))->GetValue()->bVal
#define GET_XSIPARAM_INTVAL(pParamOwner,ParamName) ((CSLVariantParameter*)pParamOwner->ParameterFromName(ParamName))->GetValue()->nVal
#define GET_XSIPARAM_STRINGVAL(pParamOwner,ParamName) ((CSLVariantParameter*)pParamOwner->ParameterFromName(ParamName))->GetValue()->p_cVal

XsiMesh::XsiMesh(const WtfEngine::StringPool::Entry &sName)
: Mesh(sName) {
};

void XsiMesh::ParseXsi(CSLMesh *pMesh) {
	using namespace std;
	
	bool bHasVnorms;
	vector<tVector> verts;
	vector<Triangle> tris;
	vector<Triangle> vnorms;
	vector<vector<fVector2D> > coordSets;

	//
	// Load all vertices from the shape
	//

	CSLBaseShape *pShape = pMesh->Shape();
	CSIBCVector3D *vVerts = pShape->GetVertexListPtr();
	verts.reserve(pShape->GetVertexCount());
	for(int i = 0; i < pShape->GetVertexCount(); i++) {
		verts.push_back(XSI_VEC_TO_VEC(vVerts[i]));
	};


	//
	// Get the triangles
	//

	if (pMesh->GetTriangleListCount() == 0) {
		std::cerr << (String("Warning: XsiMesh contains no triangles.. ") + getName()).str();
	} else {
		CSLTriangleList *pTriList = pMesh->TriangleLists()[0];
		for(int i = 0; i < pTriList->GetTriangleCount(); i++) {
			addTriangle(Triangle(verts[(*pTriList->GetVertexIndices())[i].m_iVtx1],
				verts[(*pTriList->GetVertexIndices())[i].m_iVtx2],
				verts[(*pTriList->GetVertexIndices())[i].m_iVtx3]));
		};

		//
		// Vertex normals
		//

		bHasVnorms = pTriList->GetNormalIndices() != NULL;
		if(bHasVnorms) {
			CSIBCVector3D *vNorms = pShape->GetNormalListPtr();
			for(int i = 0; i < pTriList->GetTriangleCount(); i++) {
				vnorms.push_back(Triangle(
					XSI_VEC_TO_VEC(vNorms[(*pTriList->GetNormalIndices())[i].m_iVtx1]),
					XSI_VEC_TO_VEC(vNorms[(*pTriList->GetNormalIndices())[i].m_iVtx2]),
					XSI_VEC_TO_VEC(vNorms[(*pTriList->GetNormalIndices())[i].m_iVtx3])));
			};
		};


		//
		// UV Coordinates
		// UV Coords representation changes between dotXSI 3.0 and 3.5 (or greater)
		// files. dotXSI 3.5 can handle up to 8 texture UV coords, while 3.0 is
		// restricted to only one UV set. We don't support the older version yet.
		//

		if (pShape->Type() == CSLBaseShape::SI_SHAPE) {
			throw logic_error(String("XsiMesh unsupported shape version, requires 3.5 or later..") + getName());
		};

		coordSets.resize(pTriList->GetUVArrayCount());
		for(int c = 0; c < pTriList->GetUVArrayCount(); c++) {
			CSIBCVector2D *vUVValues = ((CSLShape_35*)pShape)->UVCoordArrays()[c]->GetUVCoordListPtr();
			for(int i = 0; i < pTriList->GetTriangleCount(); i++) {
				coordSets[c].push_back(XSI_VEC_TO_VEC2(vUVValues[(*pTriList->GetUVIndices(c))[i].m_iVtx1]));
				coordSets[c].push_back(XSI_VEC_TO_VEC2(vUVValues[(*pTriList->GetUVIndices(c))[i].m_iVtx2]));
				coordSets[c].push_back(XSI_VEC_TO_VEC2(vUVValues[(*pTriList->GetUVIndices(c))[i].m_iVtx3]));
			};
		};
	};

	// Create the vertex buffer
	CreateVBO(bHasVnorms ? &vnorms : NULL, &coordSets, false);
};


XsiModel::XsiModel(const WtfEngine::StringPool::Entry &sName)
: Model(sName) {
};

/**
 * Loads the model hierarchy from the specified XSI scene.
 **/
void XsiModel::Read(CSLScene *pScene) {
	Material * pMaterial;

	// Load materials
	CSLMaterialLibrary * pMatLib = pScene->GetMaterialLibrary();
	for(int i = 0; i < pMatLib->GetMaterialCount(); i++) {
		CSLBaseMaterial * pBaseMat = pMatLib->GetMaterialList()[i];
		if(pBaseMat->Type() != CSLPrimitive::XSI_MATERIAL) {
			//throw std::logic_error(String("Unsupported XsiMaterial template in ") + getName());
			pMaterial = new Material("NULL Material");
		} else {
			XsiPhongMaterial * pMaterialP = new XsiPhongMaterial(String(getName()+":"+pBaseMat->GetName()));
			pMaterialP->ParseXsi(((CSLXSIMaterial*)pBaseMat)->GetShaderList()[0]);
			pMaterial = pMaterialP;
		};

		// XXX - argh, unmanaged pointer!
		pBaseMat->AttachUserData("Material", (void*)pMaterial);
		DataManager::GetInstance().Insert(pMaterial);
	};

	ParseXsi(pScene->Root());
};

void XsiModel::ParseXsi(CSLModel *pModel) {

	// Load transform
	if(pModel->Transform()) {
		pModel->Transform()->GetMatrix().RowMajor(mTransform[0].toArray());
	} else {
		mTransform = Transformation3D::identity();
	};

	// Recurse child models (scene hierarchy)
	CSLModel **vpChildren = pModel->GetChildrenList();
	for(int i = 0; i < pModel->GetChildrenCount(); i++) {
		XsiModel * pChild = new XsiModel(getName());
		pChild->ParseXsi(vpChildren[i]);
		mvrMesh.push_back(pChild);
	};

	switch (pModel->GetPrimitiveType() ) {
		case CSLTemplate::SI_MESH: {
			XsiMesh * pMesh = new XsiMesh(getName());
			pMesh->ParseXsi((CSLMesh*) pModel->Primitive());
			
			// Bind the material
			if(((CSLMesh*) pModel->Primitive())->GetTriangleListCount() > 0
			&& ((CSLMesh*) pModel->Primitive())->TriangleLists()[0]->GetMaterial()) {
				mvrMesh.push_back(new MaterialBinding(pModel->FullName(),
					(Material *)((CSLMesh*) pModel->Primitive())->TriangleLists()[0]
					->GetMaterial()->FindUserData("Material")->GetData(), pMesh));
			} else mvrMesh.push_back(pMesh);

			break;
		}
		case CSLTemplate::SI_CAMERA:
		case CSLTemplate::SI_DIRECTIONAL_LIGHT:
		case CSLTemplate::SI_INFINITE_LIGHT:
		case CSLTemplate::SI_POINT_LIGHT:
		case CSLTemplate::SI_SPOT_LIGHT:
		case CSLTemplate::SI_IK_ROOT:
		case CSLTemplate::SI_IK_JOINT:
		case CSLTemplate::SI_IK_EFFECTOR:
		case CSLTemplate::SI_NURBS_SURFACE:
		case CSLTemplate::SI_NURBS_CURVE:
		case CSLTemplate::SI_INSTANCE:
		case CSLTemplate::SI_NULL_OBJECT:
			break;
	};
};

void XsiModel::render() {
	WtfGraphics::SaveView();
	WtfGraphics::Transform(mTransform);
	Model::render();
	WtfGraphics::RestoreView();
};

WtfEngine::IDataObject * XsiModel::Load(const WtfEngine::StringPool::Entry &sName) {
	CSLScene * pScene = new CSLScene();
	XsiModel * pModel = new XsiModel(sName);

	if(pScene->Open((char*)(*sName).c_str()) != SI_SUCCESS
		|| pScene->Read() != SI_SUCCESS) {
		throw std::runtime_error(String("Failed to load dotXSI file: ") + sName);
	};

	pModel->Read(pScene);
	return pModel;
};



/**** Materials ****/

XsiPhongMaterial::XsiPhongMaterial(const WtfEngine::StringPool::Entry &sName)
: Material(sName) {
};

void XsiPhongMaterial::ParseXsi(CSLXSIShader *pMat) {
	// Read shading parameters
	mvDiffuse[0] = GET_XSIPARAM_FLOATVAL(pMat,"diffuse.red");
	mvDiffuse[1] = GET_XSIPARAM_FLOATVAL(pMat,"diffuse.green");
	mvDiffuse[2] = GET_XSIPARAM_FLOATVAL(pMat,"diffuse.blue");
	mvDiffuse[3] = GET_XSIPARAM_FLOATVAL(pMat,"diffuse.alpha");

	mvEmissive[0] = GET_XSIPARAM_FLOATVAL(pMat,"ambient.red");
	mvEmissive[1] = GET_XSIPARAM_FLOATVAL(pMat,"ambient.green");
	mvEmissive[2] = GET_XSIPARAM_FLOATVAL(pMat,"ambient.blue");

	mvSpecular[0] = GET_XSIPARAM_FLOATVAL(pMat,"specular.red");
	mvSpecular[1] = GET_XSIPARAM_FLOATVAL(pMat,"specular.green");
	mvSpecular[2] = GET_XSIPARAM_FLOATVAL(pMat,"specular.blue");

	//mfPower = GET_XSIPARAM_FLOATVAL(pMat, "Power");
};
